/*******************************************************************************
 * Copyright (c) 2010, 2017 Oak Ridge National Laboratory and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 * 
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Xihui Chen - initial API and implementation
 *     Torkild U. Resheim - Expose fields and methods for subclassing bug 421670
 ******************************************************************************/
package org.eclipse.nebula.visualization.xygraph.figures;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.eclipse.draw2d.Cursors;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.Polyline;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.nebula.visualization.internal.xygraph.undo.MovingAnnotationCommand;
import org.eclipse.nebula.visualization.internal.xygraph.undo.MovingAnnotationLabelCommand;
import org.eclipse.nebula.visualization.xygraph.dataprovider.IDataProvider;
import org.eclipse.nebula.visualization.xygraph.dataprovider.IDataProviderListener;
import org.eclipse.nebula.visualization.xygraph.dataprovider.ISample;
import org.eclipse.nebula.visualization.xygraph.linearscale.Range;
import org.eclipse.nebula.visualization.xygraph.util.Preferences;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;

/**
 * Annotation Figure. Annotation could be used to indicate the information for a
 * particular point.
 *
 * @author Xihui Chen
 * @author Torkild U. Resheim
 */
public class Annotation extends Figure implements IAxisListener, IDataProviderListener {
	/** The way how the cursor line will be drawn. */
	/**
	 * @author Xihui Chen
	 *
	 */
	public enum CursorLineStyle {
		NONE(0, "None"), UP_DOWN(1, "Up&Down"), LEFT_RIGHT(2, "Left&Right"), FOUR_DIRECTIONS(3, "Four Directions");

		private CursorLineStyle(int index, String description) {
			this.index = index;
			this.description = description;
		}

		private int index;
		private String description;

		@Override
		public String toString() {
			return description;
		}

		public static String[] stringValues() {
			String[] sv = new String[values().length];
			int i = 0;
			for (CursorLineStyle p : values())
				sv[i++] = p.toString();
			return sv;
		}

		/**
		 * @return the index
		 */
		public int getIndex() {
			return index;
		}

	}

	private IAnnotationLabelProvider labelProvider = null;

	protected Axis xAxis;
	protected Axis yAxis;
	private String name;
	private FontData fontData;

	private CursorLineStyle cursorLineStyle = CursorLineStyle.NONE;
	private Point currentPosition;

	protected double xValue;
	protected double yValue;

	protected Trace trace;
	protected ISample currentSnappedSample;

	private boolean showName = true;
	private boolean showSampleInfo = true;
	private boolean showPosition = true;

	private Color annotationColor = null;
	private RGB annotationColorRGB = null;

	private Label infoLabel;
	// label's relative position to currentPosition
	private double dx = 40;
	private double dy = -40;
	// label's relative center position to currentPosition
	private double x0, y0;
	private boolean knowX0Y0 = false;
	private boolean infoLabelArmed = false;

	private Pointer pointer;

	private Polyline vLine, hLine;

	private IXYGraph xyGraph;

	private final static int POINT_SIZE = 6;
	private final static int CURSOR_LINE_LENGTH = 3;
	private final static int ARROW_LINE_LENGTH = 12;
	protected boolean pointerDragged;

	private List<IAnnotationListener> listeners;

	/**
	 * Construct an annotation on a trace.
	 * 
	 * @param name
	 *            the name of the annotation.
	 * @param trace
	 *            the trace which the annotation will snap to.
	 */
	public Annotation(String name, Trace trace) {
		this(name, trace.getXAxis(), trace.getYAxis());
		this.trace = trace;
		trace.getDataProvider().addDataProviderListener(this);
	}

	/**
	 * Construct a free annotation.
	 * 
	 * @param xAxis
	 *            the xAxis of the annotation.
	 * @param yAxis
	 *            the yAxis of the annotation.
	 * @param name
	 *            the name of the annotation.
	 */
	public Annotation(String name, Axis xAxis, Axis yAxis) {
		this.xAxis = xAxis;
		this.yAxis = yAxis;
		this.name = name;
		trace = null;
		infoLabel = new Label();
		infoLabel.setOpaque(false);
		infoLabel.setCursor(Cursors.SIZEALL);
		add(infoLabel);
		InfoLabelDragger infoLabelDragger = new InfoLabelDragger();
		infoLabel.addMouseMotionListener(infoLabelDragger);
		infoLabel.addMouseListener(infoLabelDragger);
		pointer = new Pointer();
		add(pointer);
		updateToDefaultPosition();
		xAxis.addListener(this);
		yAxis.addListener(this);
	}

	public void remove() {
		if (listeners != null) {
			listeners.clear();
			listeners = null;
		}
		if (getParent()!=null) getParent().remove(this);
		xAxis.removeListener(this);
		yAxis.removeListener(this);
	}

	public synchronized void addAnnotationListener(IAnnotationListener listener) {
		if (listeners == null)
			listeners = new CopyOnWriteArrayList<IAnnotationListener>();
		listeners.add(listener);
	}

	private void fireAnnotationMoved(double oldX, double oldY, double newX, double newY) {
		if (listeners == null)
			return;
		for (IAnnotationListener listener : listeners) {
			listener.annotationMoved(oldX, oldY, newX, newY);
		}
	}

	@Override
	public boolean containsPoint(int x, int y) {

		return infoLabel.containsPoint(x, y) || pointer.containsPoint(x, y)
				|| (vLine != null ? vLine.containsPoint(x, y) : false)
				|| (hLine != null ? hLine.containsPoint(x, y) : false);
	}

	@Override
	protected void layout() {
		super.layout();
		if (trace != null && currentSnappedSample == null && !pointerDragged)
			updateToDefaultPosition();
		Dimension size = infoLabel.getPreferredSize();
		updateX0Y0Fromdxdy(size);
		Rectangle infoBounds = new Rectangle((int) (currentPosition.x + x0 - size.width / 2.0),
				(int) (currentPosition.y + y0 - size.height / 2.0), size.width, size.height);

		infoLabel.setBounds(infoBounds);

		pointer.setBounds(new Rectangle(currentPosition.x - POINT_SIZE, currentPosition.y - POINT_SIZE, 2 * POINT_SIZE,
				2 * POINT_SIZE));
		// layout Cursor Line
		switch (cursorLineStyle) {
		case NONE:
			break;
		case FOUR_DIRECTIONS:
		case LEFT_RIGHT:
			hLine.setPoints(new PointList(
					new int[] { xAxis.getValuePosition(xAxis.getRange().getLower(), false), currentPosition.y,
							xAxis.getValuePosition(xAxis.getRange().getUpper(), false), currentPosition.y }));
			if (cursorLineStyle != CursorLineStyle.FOUR_DIRECTIONS)
				break;
		case UP_DOWN:
			vLine.setPoints(new PointList(
					new int[] { currentPosition.x, yAxis.getValuePosition(yAxis.getRange().getUpper(), false),
							currentPosition.x, yAxis.getValuePosition(yAxis.getRange().getLower(), false) }));
			break;
		default:
			break;
		}
	}

	@Override
	protected void paintFigure(Graphics graphics) {
		super.paintFigure(graphics);
		if (trace != null && currentSnappedSample == null && !pointerDragged)
			updateToDefaultPosition();

		if (Preferences.useAdvancedGraphics())
			graphics.setAntialias(SWT.ON);
		Color tempColor;
		if (annotationColor == null) {
			tempColor = yAxis.getForegroundColor();
		} else
			tempColor = annotationColor;
		infoLabel.setForegroundColor(tempColor);
		pointer.setForegroundColor(tempColor);
		if (vLine != null)
			vLine.setForegroundColor(tempColor);
		if (hLine != null)
			hLine.setForegroundColor(tempColor);
		graphics.setForegroundColor(tempColor);

		if (infoLabelArmed) // draw infoLabel Armed rect
			graphics.drawRectangle(infoLabel.getBounds());
		if (showName || showPosition || showSampleInfo) {
			// draw indicate line
			graphics.drawLine(currentPosition.x + (int) dx, currentPosition.y + (int) dy, currentPosition.x,
					currentPosition.y);
			// draw Arrow
			int x1 = (int) (ARROW_LINE_LENGTH * Math.cos(Math.atan(-dy / dx) - Math.PI / 9));
			int y1 = (int) (ARROW_LINE_LENGTH * Math.sin(Math.atan(-dy / dx) - Math.PI / 9));
			if (dx < 0) {
				x1 = -x1;
				y1 = -y1;
			}
			graphics.drawLine(currentPosition.x + x1, currentPosition.y - y1, currentPosition.x, currentPosition.y);
			x1 = (int) (ARROW_LINE_LENGTH * Math.cos(Math.atan(-dy / dx) + Math.PI / 9));
			y1 = (int) (ARROW_LINE_LENGTH * Math.sin(Math.atan(-dy / dx) + Math.PI / 9));
			if (dx < 0) {
				x1 = -x1;
				y1 = -y1;
			}
			graphics.drawLine(currentPosition.x + x1, currentPosition.y - y1, currentPosition.x, currentPosition.y);
		}
		// draw Cursor Line
		switch (cursorLineStyle) {
		case NONE:
			// left
			graphics.drawLine(currentPosition.x - POINT_SIZE / 2, currentPosition.y,
					currentPosition.x - POINT_SIZE / 2 - CURSOR_LINE_LENGTH, currentPosition.y);
			// right
			graphics.drawLine(currentPosition.x + POINT_SIZE / 2, currentPosition.y,
					currentPosition.x + POINT_SIZE / 2 + CURSOR_LINE_LENGTH, currentPosition.y);
			// up
			graphics.drawLine(currentPosition.x, currentPosition.y - POINT_SIZE / 2, currentPosition.x,
					currentPosition.y - POINT_SIZE / 2 - CURSOR_LINE_LENGTH);
			// down
			graphics.drawLine(currentPosition.x, currentPosition.y + POINT_SIZE / 2, currentPosition.x,
					currentPosition.y + POINT_SIZE / 2 + CURSOR_LINE_LENGTH);
			break;
		case FOUR_DIRECTIONS:
		case LEFT_RIGHT:
			// left
			graphics.drawLine(currentPosition.x - POINT_SIZE / 2, currentPosition.y,
					xAxis.getValuePosition(xAxis.getRange().getLower(), false), currentPosition.y);
			// right
			graphics.drawLine(currentPosition.x + POINT_SIZE / 2, currentPosition.y,
					xAxis.getValuePosition(xAxis.getRange().getUpper(), false), currentPosition.y);
			if (cursorLineStyle != CursorLineStyle.FOUR_DIRECTIONS)
				break;
		case UP_DOWN:
			// up
			graphics.drawLine(currentPosition.x, currentPosition.y - POINT_SIZE / 2, currentPosition.x,
					yAxis.getValuePosition(yAxis.getRange().getUpper(), false));
			// down
			graphics.drawLine(currentPosition.x, currentPosition.y + POINT_SIZE / 2, currentPosition.x,
					yAxis.getValuePosition(yAxis.getRange().getLower(), false));
			break;
		default:
			break;
		}

	}

	/**
	 * update (x0, y0) if it is unknown.
	 * 
	 * @param size
	 *            the label size
	 * @return
	 */
	public void updateX0Y0Fromdxdy(Dimension size) {
		if (!knowX0Y0) {
			knowX0Y0 = true;
			int h = size.height;
			int w = size.width;
			if (dy != 0) {
				// assume this is the intersection
				y0 = dy - h / 2.0;
				x0 = (dx / dy) * y0;
				if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
						&& new Range(x0 - w / 2.0, x0 + w / 2.0).inRange(dx))
					return;

				y0 = dy + h / 2.0;
				x0 = (dx / dy) * y0;
				if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
						&& new Range(x0 - w / 2.0, x0 + w / 2.0).inRange(dx))
					return;
			} else {

			}
			if (dx != 0) {
				x0 = dx + w / 2.0;
				y0 = (dy / dx) * x0;
				if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
						&& new Range(y0 - h / 2.0, y0 + h / 2.0).inRange(dy))
					return;

				x0 = dx - w / 2.0;
				y0 = (dy / dx) * x0;
				if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
						&& new Range(y0 - h / 2.0, y0 + h / 2.0).inRange(dy))
					return;
			}
		} else
			return;
	}

	/**
	 * update (dx, dy) if (x0, y0) has been updated by dragging.
	 * 
	 * @param size
	 *            the label size
	 * @return
	 */
	private void updatedxdyFromX0Y0() {
		Dimension size = infoLabel.getPreferredSize();
		int h = size.height;
		int w = size.width;
		if (y0 != 0) {
			dy = y0 + h / 2.0;
			dx = x0 * dy / y0;
			if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
					&& new Range(x0 - w / 2.0, x0 + w / 2.0).inRange(dx))
				return;

			dy = y0 - h / 2.0;
			dx = x0 * dy / y0;
			if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
					&& new Range(x0 - w / 2.0, x0 + w / 2.0).inRange(dx))
				return;
		} else
			dy = 0;
		if (x0 != 0) {
			dx = x0 - size.width / 2.0;
			dy = y0 * dx / x0;
			if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
					&& new Range(y0 - h / 2.0, y0 + h / 2.0).inRange(dy))
				return;

			dx = x0 + size.width / 2.0;
			dy = y0 * dx / x0;
			if (new Range(0, x0).inRange(dx) && new Range(0, y0).inRange(dy)
					&& new Range(y0 - h / 2.0, y0 + h / 2.0).inRange(dy))
				return;
		} else
			dx = 0;
	}

	/**
	 * move the annotation to the center of the plot area or trace.
	 */
	protected void updateToDefaultPosition() {
		double oldX = xValue;
		double oldY = yValue;
		if (trace != null && trace.getHotSampleList().size() > 0) {
			currentSnappedSample = trace.getHotSampleList().get(trace.getHotSampleList().size() / 2);
			currentPosition = new Point(xAxis.getValuePosition(currentSnappedSample.getXValue(), false),
					yAxis.getValuePosition(currentSnappedSample.getYValue(), false));
			xValue = currentSnappedSample.getXValue();
			yValue = currentSnappedSample.getYValue();
		} else {
			currentSnappedSample = null;
			if (xAxis.isLogScaleEnabled())
				xValue = Math.pow(10,
						(Math.log10(xAxis.getRange().getLower()) + Math.log10(xAxis.getRange().getUpper())) / 2);
			else
				xValue = (xAxis.getRange().getLower() + xAxis.getRange().getUpper()) / 2;
			if (yAxis.isLogScaleEnabled())
				yValue = Math.pow(10,
						(Math.log10(yAxis.getRange().getLower()) + Math.log10(yAxis.getRange().getUpper())) / 2);
			else
				yValue = (yAxis.getRange().getLower() + yAxis.getRange().getUpper()) / 2;

			currentPosition = new Point(xAxis.getValuePosition(xValue, false), yAxis.getValuePosition(yValue, false));
		}
		updateInfoLabelText(true);
		if (xValue != oldX || yValue != oldY)
			revalidate();
		fireAnnotationMoved(oldX, oldY, xValue, yValue);
	}

	/**
	 * Set the position of the annotation based on plot values
	 * 
	 * @param x
	 *            Position as value on the X axis
	 * @param y
	 *            Position as value on the Y axis
	 * @see #setCurrentPosition(Point, boolean) for setting the position based
	 *      on screen coordinates
	 */
	public void setValues(final double x, final double y) {
		double oldX = xValue;
		double oldY = yValue;
		xValue = x;
		yValue = y;

		currentPosition = new Point(xAxis.getValuePosition(xValue, false), yAxis.getValuePosition(yValue, false));
		updateInfoLabelText(true);
		revalidate();
		if (oldX != xValue || oldY != yValue)
			fireAnnotationMoved(oldX, oldY, xValue, yValue);
	}

	/**
	 *
	 */
	private void updateInfoLabelText(boolean updateX0Y0) {
		String info = null;
		if (labelProvider != null) {
			info = labelProvider.getInfoText(xValue, yValue, showName, showSampleInfo, showPosition);
		}

		if (info == null) {
			info = "";
			if (showName)
				info = name;
			if (showSampleInfo && currentSnappedSample != null && !currentSnappedSample.getInfo().equals(""))
				info += "\n" + currentSnappedSample.getInfo();
			if (showPosition)
				info += "\n" + "(" + xAxis.format(xValue) + ", " + (Double.isNaN(yValue) ? "NaN" : yAxis.format(yValue))
						+ ")";
		}
		infoLabel.setText(info);
		knowX0Y0 = !updateX0Y0;
	}

	private void updateInfoLabelText() {
		updateInfoLabelText(true);
	}

	/**
	 * @param axis
	 *            the xAxis to set
	 */
	public void setXAxis(Axis axis) {
		if (this.xAxis == axis)
			return;
		xAxis = axis;
		updateToDefaultPosition();
		revalidate();
		repaint();
	}

	/**
	 * @param axis
	 *            the yAxis to set
	 */
	public void setYAxis(Axis axis) {
		if (this.yAxis == axis)
			return;
		yAxis = axis;
		updateToDefaultPosition();
		revalidate();
		repaint();
	}

	/**
	 * @param name
	 *            the name to set
	 */
	public void setName(String name) {
		this.name = name;
		updateInfoLabelText();
	}

	@Override
	public void setFont(Font f) {
		super.setFont(f);

		if (f != null)
			this.fontData = getFont().getFontData()[0];
	}

	public FontData getFontData() {
		return fontData;
	}

	/**
	 * @param trace
	 *            the trace to set
	 */
	public void setTrace(Trace trace) {
		if (this.trace == trace)
			return;
		this.xAxis = trace.getXAxis();
		this.yAxis = trace.getYAxis();
		if (!isFree() && this.trace != trace)
			this.trace.getDataProvider().removeDataProviderListener(this);
		if (isFree() || this.trace != trace) {
			this.trace = trace;
			updateToDefaultPosition();
		}
		this.trace = trace;
		trace.getDataProvider().addDataProviderListener(this);
		repaint();
	}

	/**
	 * Make the annotation free.
	 * 
	 * @param xAxis
	 * @param yAxis
	 */
	public void setFree(Axis xAxis, Axis yAxis) {
		if (trace != null) {
			trace.getDataProvider().removeDataProviderListener(this);
			trace = null;
			updateToDefaultPosition();
		}
		setXAxis(xAxis);
		setYAxis(yAxis);
		revalidate();
		repaint();
	}

	/**
	 * @return true if the annotation is free.
	 */
	public boolean isFree() {
		return trace == null;
	}

	/**
	 * @param showName
	 *            the showName to set
	 */
	public void setShowName(boolean showName) {
		this.showName = showName;
		updateInfoLabelText();
	}

	/**
	 * @param showSampleInfo
	 *            the showSampleInfo to set
	 */
	public void setShowSampleInfo(boolean showSampleInfo) {
		this.showSampleInfo = showSampleInfo;
		updateInfoLabelText();
	}

	/**
	 * @param showPosition
	 *            the showPosition to set
	 */
	public void setShowPosition(boolean showPosition) {
		this.showPosition = showPosition;
		updateInfoLabelText();
	}

	/**
	 * @param annotationColor
	 *            the annotationColor to set
	 */
	public void setAnnotationColor(Color annotationColor) {
		this.annotationColor = annotationColor;

		if (annotationColor != null)
			this.annotationColorRGB = annotationColor.getRGB();

		infoLabel.setForegroundColor(annotationColor);
		pointer.setForegroundColor(annotationColor);
	}

	/**
	 * @param annotationFont
	 *            the annotationFont to set
	 */
	public void setAnnotationFont(Font annotationFont) {
		infoLabel.setFont(annotationFont);
	}

	/**
	 * @return annotation font
	 */
	public Font getAnnotationFont() {
		return infoLabel.getFont();
	}

	/**
	 * @param cursorLineStyle
	 *            the cursorLineStyle to set
	 */
	public void setCursorLineStyle(CursorLineStyle cursorLineStyle) {
		this.cursorLineStyle = cursorLineStyle;
		if (cursorLineStyle == CursorLineStyle.UP_DOWN || cursorLineStyle == CursorLineStyle.FOUR_DIRECTIONS) {
			if (vLine == null) {
				vLine = new Polyline();
				vLine.setCursor(Cursors.SIZEWE);
				AnnotationDragger annotationDragger = new AnnotationDragger(true, false);
				vLine.addMouseListener(annotationDragger);
				vLine.addMouseMotionListener(annotationDragger);
				vLine.setTolerance(3);
				add(vLine, 0);
			}
		}
		if (cursorLineStyle == CursorLineStyle.LEFT_RIGHT || cursorLineStyle == CursorLineStyle.FOUR_DIRECTIONS) {
			if (hLine == null) {
				hLine = new Polyline();
				hLine.setCursor(Cursors.SIZENS);
				AnnotationDragger annotationDragger = new AnnotationDragger(false, true);
				hLine.addMouseListener(annotationDragger);
				hLine.addMouseMotionListener(annotationDragger);
				hLine.setTolerance(3);
				add(hLine, 0);
			}
		}
		if (cursorLineStyle == CursorLineStyle.UP_DOWN) {
			if (hLine != null) {
				remove(hLine);
				hLine = null;
			}
		}
		if (cursorLineStyle == CursorLineStyle.LEFT_RIGHT) {
			if (vLine != null) {
				remove(vLine);
				vLine = null;
			}
		}
	}

	/**
	 * @param currentPosition
	 *            the currentPosition to set
	 */
	public void setCurrentPosition(Point currentPosition, boolean keepLabelPosition, boolean calcValueFromPosition) {
		if (keepLabelPosition) {
			int deltaX = this.currentPosition.x - currentPosition.x;
			int deltaY = this.currentPosition.y - currentPosition.y;
			x0 += deltaX;
			y0 += deltaY;
			knowX0Y0 = true;
			updatedxdyFromX0Y0();
		}
		this.currentPosition = currentPosition;
		if (calcValueFromPosition) {
			double oldX = xValue;
			double oldY = yValue;
			xValue = xAxis.getPositionValue(currentPosition.x, false);
			yValue = yAxis.getPositionValue(currentPosition.y, false);
			fireAnnotationMoved(oldX, oldY, xValue, yValue);
		}
		updateInfoLabelText(keepLabelPosition);
		revalidate();
		repaint();
	}

	public void setCurrentPosition(Point currentPosition, boolean keepLabelPosition) {
		setCurrentPosition(currentPosition, keepLabelPosition, true);
	}

	/**
	 * @param currentSnappedSample
	 *            the currentSnappedSample to set
	 * @param keepLabelPosition
	 */
	public void setCurrentSnappedSample(ISample currentSnappedSample, boolean keepLabelPosition) {
		if (!trace.getHotSampleList().contains(currentSnappedSample))
			updateToDefaultPosition();
		else {
			this.currentSnappedSample = currentSnappedSample;
			Point newPosition = new Point(xAxis.getValuePosition(currentSnappedSample.getXValue(), false),
					yAxis.getValuePosition(currentSnappedSample.getYValue(), false));
			double oldX = xValue;
			double oldY = yValue;
			xValue = currentSnappedSample.getXValue();
			yValue = currentSnappedSample.getYValue();
			if (Double.isNaN(currentSnappedSample.getXPlusError()))
				yValue = Double.NaN;
			setCurrentPosition(newPosition, keepLabelPosition, false);
			fireAnnotationMoved(oldX, oldY, xValue, yValue);
		}
		repaint();
	}

	public void axisRevalidated(Axis axis) {
		currentPosition = new Point(xAxis.getValuePosition(xValue, false), yAxis.getValuePosition(yValue, false));
		updateInfoLabelText();
		if (getParent() != null)
			layout();
	}

	public void axisRangeChanged(Axis axis, Range old_range, Range new_range) {
		// do nothing
	}

	public void dataChanged(IDataProvider dataProvider) {
		if (trace == null)
			return;
		// run it in next cycle after the trace have been painted.
		Display.getCurrent().asyncExec(new Runnable() {
			@Override
			public void run() {
				if (trace.getHotSampleList().contains(currentSnappedSample)) {
					double oldX = xValue;
					double oldY = yValue;
					if (yValue != currentSnappedSample.getYValue()) { // When
																		// waveform
																		// index
																		// is
																		// changed,
																		// Y
																		// value
																		// of
																		// the
																		// snapped
																		// sample
																		// is
																		// also
																		// changed.
																		// In
																		// that
																		// case,
																		// the
																		// position
																		// of
																		// this
																		// annotation
																		// must
																		// be
																		// updated
																		// accordingly.
						yValue = currentSnappedSample.getYValue();
					}
					if (xValue != currentSnappedSample.getXValue()) {
						xValue = currentSnappedSample.getXValue();
					}
					currentPosition = new Point(xAxis.getValuePosition(xValue, false),
							yAxis.getValuePosition(yValue, false));
					fireAnnotationMoved(oldX, oldY, xValue, yValue);
				} else if (trace.getHotSampleList().size() > 0) {
					updateToDefaultPosition();
					pointerDragged = false;
				}
			}
		});

	}

	/**
	 * @param xyGraph
	 *            the xyGraph to set
	 */
	public void setXYGraph(IXYGraph xyGraph) {
		this.xyGraph = xyGraph;
	}

	public void setdxdy(double dx, double dy) {
		this.dx = dx;
		this.dy = dy;
		knowX0Y0 = false;
		repaint();
	}

	/**
	 * Sets x an y location.
	 *
	 * @param x
	 * @param y
	 */
	public void setLocation(double x, double y) {
		this.xValue = x;
		this.yValue = y;
		repaint();
	}

	/**
	 * @return the xAxis
	 */
	public Axis getXAxis() {
		return xAxis;
	}

	/**
	 * @return the yAxis
	 */
	public Axis getYAxis() {
		return yAxis;
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	/** @return X value, i.e. value of this annotation on the X Axis */
	public double getXValue() {
		return xValue;
	}

	/** @return Y value, i.e. value of this annotation on the Y Axis */
	public double getYValue() {
		return yValue;
	}

	/**
	 * @return the cursorLineStyle
	 */
	public CursorLineStyle getCursorLineStyle() {
		return cursorLineStyle;
	}

	/**
	 * @return the trace
	 */
	public Trace getTrace() {
		return trace;
	}

	/**
	 * @return the showName
	 */
	public boolean isShowName() {
		return showName;
	}

	/**
	 * @return the showSampleInfo
	 */
	public boolean isShowSampleInfo() {
		return showSampleInfo;
	}

	/**
	 * @return the showPosition
	 */
	public boolean isShowPosition() {
		return showPosition;
	}

	/**
	 * @return the annotationColor
	 */
	public Color getAnnotationColor() {
		return annotationColor;
	}

	class InfoLabelDragger extends MouseMotionListener.Stub implements MouseListener {

		private MovingAnnotationLabelCommand command;

		@Override
		public void mouseDragged(MouseEvent me) {
			x0 = me.getLocation().x - currentPosition.x;
			y0 = me.getLocation().y - currentPosition.y;
			knowX0Y0 = true;
			updatedxdyFromX0Y0();
			Annotation.this.revalidate();
			Annotation.this.repaint();
			me.consume();
		}

		public void mouseDoubleClicked(MouseEvent me) {
		}

		public void mousePressed(MouseEvent me) {
			command = new MovingAnnotationLabelCommand(Annotation.this);
			command.setBeforeMovingDxDy(dx, dy);
			infoLabelArmed = true;
			Annotation.this.revalidate();
			Annotation.this.repaint();
			me.consume(); // it must be consumed to make dragging smoothly.
		}

		public void mouseReleased(MouseEvent me) {
			command.setAfterMovingDxDy(dx, dy);
			xyGraph.getOperationsManager().addCommand(command);
			infoLabelArmed = false;
			Annotation.this.revalidate();
			Annotation.this.repaint();
			me.consume();
		}

	}

	private class AnnotationDragger extends MouseMotionListener.Stub implements MouseListener {

		private MovingAnnotationCommand command;

		private boolean horizontalMoveable;
		private boolean verticalMoveable;

		public AnnotationDragger(boolean horizontalMoveable, boolean verticalMoveable) {
			this.horizontalMoveable = horizontalMoveable;
			this.verticalMoveable = verticalMoveable;
		}

		@Override
		public void mouseDragged(MouseEvent me) {
			Point mouseLocation = new Point(horizontalMoveable ? me.getLocation().x : currentPosition.x,
					verticalMoveable ? me.getLocation().y : currentPosition.y);
			// free
			if (trace == null) {
				setCurrentPosition(mouseLocation, me.getState() == (SWT.BUTTON1 | SWT.CONTROL));
			} else { // snap to trace
						// double tempX =
						// xAxis.getPositionValue(me.getLocation().x, false);
						// double tempY =
						// yAxis.getPositionValue(me.getLocation().y, false);
				ISample tempSample = null;
				double minD = Double.POSITIVE_INFINITY;
				double d;
				for (ISample s : trace.getHotSampleList()) {
					d = Math.sqrt(Math.pow(xAxis.getValuePosition(s.getXValue(), false) - mouseLocation.x, 2)
							+ Math.pow(yAxis.getValuePosition(s.getYValue(), false) - mouseLocation.y, 2));
					if (minD > d) {
						minD = d;
						tempSample = s;
					}
				}
				if (tempSample != null && currentSnappedSample != tempSample)
					setCurrentSnappedSample(tempSample, me.getState() == (SWT.BUTTON1 | SWT.CONTROL));
				else if (tempSample == null) {
					setCurrentPosition(mouseLocation, me.getState() == (SWT.BUTTON1 | SWT.CONTROL));
					pointerDragged = true;
				}

			}
			me.consume();
		}

		public void mouseDoubleClicked(MouseEvent me) {
		}

		public void mousePressed(MouseEvent me) {
			command = new MovingAnnotationCommand(Annotation.this);
			if (isFree())
				command.setBeforeMovePosition(currentPosition);
			else
				command.setBeforeMoveSnappedSample(currentSnappedSample);
			command.setBeforeDxDy(dx, dy);
			me.consume(); // it must be consumed to make dragging smoothly.
		}

		public void mouseReleased(MouseEvent me) {
			if (command != null) {
				if (isFree())
					command.setAfterMovePosition(currentPosition);
				else
					command.setAfterMoveSnappedSample(currentSnappedSample);
				command.setAfterDxDy(dx, dy);
				xyGraph.getOperationsManager().addCommand(command);
			}

		}
	}

	class Pointer extends Figure {

		public Pointer() {
			setCursor(Cursors.CROSS);
			AnnotationDragger dragger = new AnnotationDragger(true, true);
			addMouseMotionListener(dragger);
			addMouseListener(dragger);
		}

		@Override
		protected void paintClientArea(Graphics graphics) {
			super.paintClientArea(graphics);
			if (Preferences.useAdvancedGraphics())
				graphics.setAntialias(SWT.ON);
			// draw X-cross point
			Rectangle clientArea = getClientArea().getCopy().shrink(POINT_SIZE / 2, POINT_SIZE / 2);
			graphics.drawLine(clientArea.x, clientArea.y, clientArea.x + clientArea.width,
					clientArea.y + clientArea.height);
			graphics.drawLine(clientArea.x + clientArea.width, clientArea.y, clientArea.x,
					clientArea.y + clientArea.height);

		}
	}

	public IAnnotationLabelProvider getLabelProvider() {
		return labelProvider;
	}

	public void setLabelProvider(IAnnotationLabelProvider labelProvider) {
		this.labelProvider = labelProvider;
	}

	public void axisForegroundColorChanged(Axis axis, Color oldColor, Color newColor) {

	}

	public void axisTitleChanged(Axis axis, String oldTitle, String newTitle) {

	}

	public void axisAutoScaleChanged(Axis axis, boolean oldAutoScale, boolean newAutoScale) {

	}

	public void axisLogScaleChanged(Axis axis, boolean old, boolean logScale) {

	}

	public RGB getAnnotationColorRGB() {
		return annotationColorRGB;
	}

	/**
	 * Sets annotation order to front
	 */
	public void toFront() {
		final IFigure par = getParent();
		if (par != null) {
			par.remove(this);
			final int end = par.getChildren() != null ? par.getChildren().size() : 0;
			par.add(this, end);
		}
	}

}
